package main;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class Interpreter {

	/**
	 * Symbol table with the name, id and overflow.
	 */
	private ArrayList<Symbol> symbolTable = new ArrayList<Symbol>();
	/**
	 * Segment that has the generated assembly code inside it.
	 */
	private Segment segment;
	
	/**
	 * 
	 * @param sym The symbol table to pass in.
	 * @param seg Segment that has the generated assembly code inside it.
	 */
	public Interpreter(ArrayList<Symbol> sym, Segment seg) {
		symbolTable.addAll(sym);
		segment = seg;
	}
	
	/**
	 * Uses the assembly code and runs the machine.
	 * @throws IOException
	 */
	public void interpret() throws IOException {
		int pc = 0;
		int stackPointer = 0;
		int stack_size = 500;
		int stack[] = new int[stack_size];

		while (pc < segment.code.size()) {
			Instr i = segment.code.get(pc);

			switch(i.function) {
			case Segment.lit:
				//put number on the stack
				stack[stackPointer] = i.address;
				stackPointer++;
				break;
			case Segment.opReturn:    // return
				break;
			case Segment.opUnaryMinus:
				stackPointer--;
				stack[stackPointer] = -stack[stackPointer];
				stackPointer++;
				break;
			case Segment.opPlus:
				stackPointer--;
				if (i.constant == 0) {
					stack[stackPointer] += symbolTable.get(i.address).num;
				} else {
					stack[stackPointer] += i.address;
				}
				stackPointer++;
				break;
			case Segment.opMinus:
				stackPointer--;
				if (i.constant == 0) {
					stack[stackPointer] -= symbolTable.get(i.address).num;
				} else {
					stack[stackPointer] -= i.address;
				}
				stackPointer++;
				break;
			case Segment.opTimes:
				stackPointer--;
				if (i.constant == 0) {
					stack[stackPointer] *= symbolTable.get(i.address).num;
				} else {
					stack[stackPointer] *= i.address;
				}
				stackPointer++;
				break;
			case Segment.opDiv:
				//checks for division by zero
				stackPointer--;
				if (i.constant == 0) {
					if (symbolTable.get(i.address).num != 0)
						stack[stackPointer] /= symbolTable.get(i.address).num;
					else
						error("cant divide by zero");
				} else {
					if(i.address != 0)
						stack[stackPointer] /= i.address;
					else
						error("cant divide by zero");
				}
				stackPointer++;
				break;  
			case Segment.opOdd:
				break;
			case Segment.opEqual:
				stackPointer--;
				if (i.constant == 0 && segment.code.get(pc-1).function != Segment.opUnaryMinus && segment.code.get(pc-1).function != Segment.lit) {
					stack[stackPointer] = bool2int(stack[stackPointer] == (symbolTable.get(i.address)).num);
				} else {
					if (segment.code.get(pc-1).function == Segment.opUnaryMinus || segment.code.get(pc-1).function == Segment.lit)
						stack[stackPointer] = bool2int(stack[stackPointer-1] == stack[stackPointer]);
					else
						stack[stackPointer] = bool2int(stack[stackPointer] == i.address);						
				}
				break;
			case Segment.opNotEqual:
				stackPointer--;
				if (i.constant == 0 && segment.code.get(pc-1).function != Segment.opUnaryMinus && segment.code.get(pc-1).function != Segment.lit) {
					stack[stackPointer] = bool2int(stack[stackPointer] != (symbolTable.get(i.address)).num);
				} else {
					if (segment.code.get(pc-1).function == Segment.opUnaryMinus || segment.code.get(pc-1).function == Segment.lit)
						stack[stackPointer] = bool2int(stack[stackPointer-1] != stack[stackPointer]);
					else
						stack[stackPointer] = bool2int(stack[stackPointer] != i.address);
				}
				break;
			case Segment.opLess:
				stackPointer--;
				if (i.constant == 0 && segment.code.get(pc-1).function != Segment.opUnaryMinus && segment.code.get(pc-1).function != Segment.lit) {
					stack[stackPointer] = bool2int(stack[stackPointer] < (symbolTable.get(i.address)).num);
				} else {
					if (segment.code.get(pc-1).function == Segment.opUnaryMinus || segment.code.get(pc-1).function == Segment.lit)
						stack[stackPointer] = bool2int(stack[stackPointer-1] < stack[stackPointer]);
					else
						stack[stackPointer] = bool2int(stack[stackPointer] < i.address);
				}
				break;
			case Segment.opGreaterEqual:
				stackPointer--;
				if (i.constant == 0 && segment.code.get(pc-1).function != Segment.opUnaryMinus && segment.code.get(pc-1).function != Segment.lit) {
					stack[stackPointer] = bool2int(stack[stackPointer] >= (symbolTable.get(i.address)).num);
				} else {
					if (segment.code.get(pc-1).function == Segment.opUnaryMinus || segment.code.get(pc-1).function == Segment.lit)
						stack[stackPointer] = bool2int(stack[stackPointer-1] >= stack[stackPointer]);
					else
						stack[stackPointer] = bool2int(stack[stackPointer] >= i.address);						
				}
				break;
			case Segment.opGreater:
				stackPointer--;
				if (i.constant == 0 && segment.code.get(pc-1).function != Segment.opUnaryMinus && segment.code.get(pc-1).function != Segment.lit) {
					stack[stackPointer] = bool2int(stack[stackPointer] > (symbolTable.get(i.address)).num);
				} else {
					if (segment.code.get(pc-1).function == Segment.opUnaryMinus || segment.code.get(pc-1).function == Segment.lit)
						stack[stackPointer] = bool2int(stack[stackPointer-1] > stack[stackPointer]);
					else
						stack[stackPointer] = bool2int(stack[stackPointer] > i.address);						
				}
				break;
			case Segment.opLessEqual:
				stackPointer--;
				if (i.constant == 0 && segment.code.get(pc-1).function != Segment.opUnaryMinus && segment.code.get(pc-1).function != Segment.lit) {
					stack[stackPointer] = bool2int(stack[stackPointer] <= (symbolTable.get(i.address)).num);
				} else {
					if (segment.code.get(pc-1).function == Segment.opUnaryMinus || segment.code.get(pc-1).function == Segment.lit)
						stack[stackPointer] = bool2int(stack[stackPointer-1] <= stack[stackPointer]);
					else
						stack[stackPointer] = bool2int(stack[stackPointer] <= i.address);						
				}
				break;
			case Segment.opWrite:
				System.out.println(symbolTable.get(i.address).num);
				break;
			case Segment.opNewLine:

				break;
			case Segment.opRead:  
				System.out.print(">");
				BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
				String src_num = in.readLine();
				stack[stackPointer] = Integer.valueOf(src_num).intValue();
				stackPointer++;
				break;
			case Segment.lod:
				stack[stackPointer] = (symbolTable.get(i.address)).num;
				stackPointer++;
				break;
			case Segment.sto:
				(symbolTable.get(i.address)).num = stack[stackPointer-1];
				stackPointer--;
				break;
			case Segment.inct:
				break;
			case Segment.jmp:
				pc = i.address - 1;
				break;
			case Segment.jpc:
				if (stack[stackPointer] == 0) {
					pc = i.address - 1;
				}
				break;
			case Segment.end:
				System.out.println("Complete");
			} // end case function
			pc++;
		} // end while pc < Segment.code.size()
	}

	/**
	 * Converts an integer boolean comparison to a integer expression
	 * @param b boolean integer expression
	 * @return 1 if true false otherwise
	 */
	int bool2int(boolean b) {
		if (b) {
			return 1;
		} else {
			return 0;
		}
	}

	/**
	 * Prints the symbol table in readable format.
	 */
	public void printSymbolTable() {
		int i = 0;
		Machine.out.println("\t\tSymbolname \tID \toverflow");
		while(i < symbolTable.size()) {
			Machine.out.println("Symbol" + i + ":\t" + symbolTable.get(i).id + "\t\t" + symbolTable.get(i).num + "\t" + symbolTable.get(i).overflow);
//			System.out.println("Symbol" + i + ":\t" + symbolTable.get(i).id + "\t\t" + symbolTable.get(i).num + "\t" + symbolTable.get(i).overflow);
			i++;
		}
	}

	/**
	 * prints error
	 * @param token error string
	 */
	void error(String token) {
		System.out.println("*** error during interpretation " + token + "***\n");
		Thread.dumpStack();
		System.exit(0);
	}
}
